#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/fs.h>

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/select.h>

#include <libaio.h>

#include "bmap.h"
#include "time.h"

//*****************************************************************************************



#define AIO_BLKSIZE	(64*1024)
#define AIO_MAXIO	32


struct iocb **iocb_free;	/* array of pointers to iocb */
int iocb_free_count;		/* current free count */

int init_iocb(int n, int iosize)
{
	void *buf;
	int i;

	if ((iocb_free = (struct iocb **)malloc(n * sizeof(struct iocb *))) == 0) {
		return -1;
	}

	for (i = 0; i < n; i++) {
		if (!(iocb_free[i] = (struct iocb *) malloc(sizeof(struct iocb))))
			return -1;
		if (posix_memalign(&buf, 512, iosize))
			return -1;
		
		io_prep_pread(iocb_free[i], -1, buf, iosize, 0);
	}
	iocb_free_count = i;
	return 0;
}


struct iocb *alloc_iocb()
{
	if (!iocb_free_count)
		return 0;
	return iocb_free[--iocb_free_count];
}

void free_iocb(struct iocb *io)
{
	iocb_free[iocb_free_count++] = io;
}

int delete_iocb()
{
	struct iocb *io;
	while(!!(io=alloc_iocb())){
		free(io->u.c.buf);
		free(io);
	}
	free(iocb_free);
	return 0;
}


/*
 * io_wait_run() - wait for an io_event and then call the callback.
 */
int io_wait_run(io_context_t ctx, struct timespec *to)
{
	struct io_event events[AIO_MAXIO];
	struct io_event *ep;
	int ret, n;

	/*
	 * get up to aio_maxio events at a time.
	 */
	ret = n = io_getevents(ctx, 1, AIO_MAXIO, events, to);

	/*
	 * Call the callback functions for each event.
	 */
	for (ep = events; n-- > 0; ep++) {
		io_callback_t cb = (io_callback_t)ep->data;
		struct iocb *iocb = ep->obj;
		cb(ctx, iocb, ep->res, ep->res2);
	}
	return ret;
}

/* Fatal error handler */
static void io_error(const char *func, int rc)
{
	if (rc == -ENOSYS)
		fprintf(stderr, "AIO not in this kernel\n");
	else if (rc < 0)
		fprintf(stderr, "%s: %s\n", func, strerror(-rc));
	else
		fprintf(stderr, "%s: error %d\n", func, rc);

	exit(1);
}

/*
 * Read complete callback.
 * Change read iocb into a write iocb and start it.
 */
static void rd_done(io_context_t ctx, struct iocb *iocb, long res, long res2)
{
	/* library needs accessors to look at iocb? */
	unsigned long iosize = iocb->u.c.nbytes;
	//char *buf = (char *)iocb->u.c.buf;
	//unsigned long long offset = iocb->u.c.offset;

	if (res2 != 0)
		io_error("aio read", res2);
	if (((unsigned long)res) != iosize) {
		fprintf(stderr, "read missing bytes expect %lu got %ld\n",
			iocb->u.c.nbytes, res);
		exit(1);
	}
	//printf("READ DONE: offset=%llu, iosize=%lu\n", offset/4096, iosize/4096);
	free_iocb(iocb);

}


double bmap_replay(struct bmap *bm, const char *devname)
{
	int srcfd, rc;

	srcfd = open(devname,O_RDONLY | O_DIRECT | O_NOATIME);
	
	io_context_t myctx;
	
	/* initialize state machine */
	io_queue_init(AIO_MAXIO, &myctx);
	
	if(init_iocb(AIO_MAXIO, AIO_BLKSIZE) < 0) {
		fprintf(stderr, "Error allocating the i/o buffers\n");
		exit(1);
	}
	struct iocb *ioq[AIO_MAXIO];
	int ioq_length=0;
	int busy=0;
	struct bmap_extent *e;
	uint64_t starttime = gettime_ns();
	for(e=bm->head; e!=NULL; e=e->next){
		uint64_t offset = e->offset * bm->blksize;
		uint64_t length = e->length * bm->blksize;
		while(length > 0){
			struct iocb *io;
			while((io=alloc_iocb())==NULL){
				if((rc = io_submit(myctx, ioq_length, ioq))<0){
					io_error("io_submit", rc);
				}
				busy += ioq_length;
				ioq_length = 0;
				busy -= io_wait_run(myctx, 0);
			}
			int size;
			if(length>AIO_BLKSIZE){
				size = AIO_BLKSIZE;
			}else{
				size = length;
			}
			io_prep_pread(io, srcfd, io->u.c.buf, size, offset);
			io_set_callback(io, rd_done);
			length -= size;
			offset += size;
			ioq[ioq_length++]=io;
		}
	}
	if((rc = io_submit(myctx, ioq_length, ioq))<0){
		io_error("io_submit", rc);
	}
	busy += ioq_length;
	struct timespec to;
	to.tv_sec = 1;
	to.tv_nsec = 0;
	while((busy-=io_wait_run(myctx, &to))>0);
	uint64_t accesstime = gettime_ns() - starttime;
	//printf("AIO ACCESS TIME: %lf ms\n", accesstime/1000000.0);
	delete_iocb();
	io_destroy(myctx);
	close(srcfd);
	return (accesstime/1000000.0);
}


//*****************************************************************************************



